View Javadoc

1   package org.apache.maven.surefire.testng;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.maven.surefire.booter.ProviderParameterNames;
23  import org.apache.maven.surefire.report.RunListener;
24  import org.apache.maven.surefire.testng.conf.Configurator;
25  import org.apache.maven.surefire.testset.TestSetFailedException;
26  import org.apache.maven.surefire.util.ReflectionUtils;
27  import org.apache.maven.surefire.util.internal.StringUtils;
28  import org.testng.TestNG;
29  import org.testng.annotations.Test;
30  import org.testng.xml.XmlClass;
31  import org.testng.xml.XmlMethodSelector;
32  import org.testng.xml.XmlSuite;
33  import org.testng.xml.XmlTest;
34  
35  import java.io.File;
36  import java.lang.annotation.Annotation;
37  import java.lang.reflect.Constructor;
38  import java.lang.reflect.InvocationTargetException;
39  import java.lang.reflect.Method;
40  import java.util.ArrayList;
41  import java.util.HashMap;
42  import java.util.List;
43  import java.util.Map;
44  
45  /**
46   * Contains utility methods for executing TestNG.
47   *
48   * @author <a href="mailto:brett@apache.org">Brett Porter</a>
49   * @author <a href='mailto:the[dot]mindstorm[at]gmail[dot]com'>Alex Popescu</a>
50   */
51  public class TestNGExecutor
52  {
53      /** The default name for a suite launched from the maven surefire plugin */
54      public static final String DEFAULT_SUREFIRE_SUITE_NAME = "Surefire suite";
55  
56      /** The default name for a test launched from the maven surefire plugin */
57      public static final String DEFAULT_SUREFIRE_TEST_NAME = "Surefire test";
58  
59      private static final boolean HAS_TEST_ANNOTATION_ON_CLASSPATH =
60          null != ReflectionUtils.tryLoadClass( TestNGExecutor.class.getClassLoader(), "org.testng.annotations.Test" );
61  
62      private TestNGExecutor()
63      {
64          // noop
65      }
66  
67      public static void run( Class[] testClasses, String testSourceDirectory, Map options, RunListener reportManager,
68                              TestNgTestSuite suite, File reportsDirectory, final String methodNamePattern )
69          throws TestSetFailedException
70      {
71          TestNG testng = new TestNG( true );
72  
73          Configurator configurator = getConfigurator( (String) options.get( "testng.configurator" ) );
74          System.out.println( "Configuring TestNG with: " + configurator.getClass().getSimpleName() );
75  
76          XmlMethodSelector groupMatchingSelector = getGroupMatchingSelector( options );
77          XmlMethodSelector methodNameFilteringSelector = getMethodNameFilteringSelector( methodNamePattern );
78  
79          Map<String, SuiteAndNamedTests> suitesNames = new HashMap<String, SuiteAndNamedTests>();
80  
81          List<XmlSuite> xmlSuites = new ArrayList<XmlSuite>();
82          for ( Class testClass : testClasses )
83          {
84              TestMetadata metadata = findTestMetadata( testClass );
85  
86              SuiteAndNamedTests suiteAndNamedTests = suitesNames.get( metadata.suiteName );
87              if ( suiteAndNamedTests == null )
88              {
89                  suiteAndNamedTests = new SuiteAndNamedTests();
90                  suiteAndNamedTests.xmlSuite.setName( metadata.suiteName );
91                  configurator.configure( suiteAndNamedTests.xmlSuite, options );
92                  xmlSuites.add( suiteAndNamedTests.xmlSuite );
93  
94                  suitesNames.put( metadata.suiteName, suiteAndNamedTests );
95              }
96  
97              XmlTest xmlTest = suiteAndNamedTests.testNameToTest.get( metadata.testName );
98              if ( xmlTest == null )
99              {
100                 xmlTest = new XmlTest( suiteAndNamedTests.xmlSuite );
101                 xmlTest.setName( metadata.testName );
102                 addSelector( xmlTest, groupMatchingSelector );
103                 addSelector( xmlTest, methodNameFilteringSelector );
104                 xmlTest.setXmlClasses( new ArrayList<XmlClass>() );
105 
106                 suiteAndNamedTests.testNameToTest.put( metadata.testName, xmlTest );
107             }
108 
109             xmlTest.getXmlClasses().add( new XmlClass( testClass.getName() ) );
110         }
111 
112         testng.setXmlSuites( xmlSuites );
113         configurator.configure( testng, options );
114         postConfigure( testng, testSourceDirectory, reportManager, suite, reportsDirectory );
115         testng.run();
116     }
117 
118     private static TestMetadata findTestMetadata( Class testClass )
119     {
120         TestMetadata result = new TestMetadata();
121         if ( HAS_TEST_ANNOTATION_ON_CLASSPATH )
122         {
123             Test testAnnotation = findAnnotation( testClass, Test.class );
124             if ( null != testAnnotation )
125             {
126                 if ( !StringUtils.isBlank( testAnnotation.suiteName() ) )
127                 {
128                     result.suiteName = testAnnotation.suiteName();
129                 }
130 
131                 if ( !StringUtils.isBlank( testAnnotation.testName() ) )
132                 {
133                     result.testName = testAnnotation.testName();
134                 }
135             }
136         }
137         return result;
138     }
139 
140     private static <T extends Annotation> T findAnnotation( Class<?> clazz, Class<T> annotationType )
141     {
142         if ( clazz == null )
143         {
144             return null;
145         }
146 
147         T result = clazz.getAnnotation( annotationType );
148         if ( result != null )
149         {
150             return result;
151         }
152 
153         return findAnnotation( clazz.getSuperclass(), annotationType );
154     }
155 
156     private static class TestMetadata
157     {
158         private String testName = DEFAULT_SUREFIRE_TEST_NAME;
159 
160         private String suiteName = DEFAULT_SUREFIRE_SUITE_NAME;
161     }
162 
163     private static class SuiteAndNamedTests
164     {
165         private XmlSuite xmlSuite = new XmlSuite();
166 
167         private Map<String, XmlTest> testNameToTest = new HashMap<String, XmlTest>();
168     }
169 
170     private static void addSelector( XmlTest xmlTest, XmlMethodSelector selector )
171     {
172         if ( selector != null )
173         {
174             xmlTest.getMethodSelectors().add( selector );
175         }
176     }
177 
178     private static XmlMethodSelector getMethodNameFilteringSelector( String methodNamePattern )
179         throws TestSetFailedException
180     {
181         if ( StringUtils.isBlank( methodNamePattern ) )
182         {
183             return null;
184         }
185 
186         // the class is available in the testClassPath
187         String clazzName = "org.apache.maven.surefire.testng.utils.MethodSelector";
188         try
189         {
190             Class clazz = Class.forName( clazzName );
191 
192             Method method = clazz.getMethod( "setMethodName", new Class[] { String.class } );
193             method.invoke( null, methodNamePattern );
194         }
195         catch ( ClassNotFoundException e )
196         {
197             throw new TestSetFailedException( e.getMessage(), e );
198         }
199         catch ( SecurityException e )
200         {
201             throw new TestSetFailedException( e.getMessage(), e );
202         }
203         catch ( NoSuchMethodException e )
204         {
205             throw new TestSetFailedException( e.getMessage(), e );
206         }
207         catch ( IllegalArgumentException e )
208         {
209             throw new TestSetFailedException( e.getMessage(), e );
210         }
211         catch ( IllegalAccessException e )
212         {
213             throw new TestSetFailedException( e.getMessage(), e );
214         }
215         catch ( InvocationTargetException e )
216         {
217             throw new TestSetFailedException( e.getMessage(), e );
218         }
219 
220         XmlMethodSelector xms = new XmlMethodSelector();
221 
222         xms.setName( clazzName );
223         // looks to need a high value
224         xms.setPriority( 10000 );
225 
226         return xms;
227     }
228 
229     private static XmlMethodSelector getGroupMatchingSelector( Map options )
230         throws TestSetFailedException
231     {
232         String groups = (String) options.get( ProviderParameterNames.TESTNG_GROUPS_PROP );
233         String excludedGroups = (String) options.get( ProviderParameterNames.TESTNG_EXCLUDEDGROUPS_PROP );
234 
235         if ( groups == null && excludedGroups == null )
236         {
237             return null;
238         }
239 
240         // the class is available in the testClassPath
241         String clazzName = "org.apache.maven.surefire.testng.utils.GroupMatcherMethodSelector";
242         try
243         {
244             Class clazz = Class.forName( clazzName );
245 
246             // HORRIBLE hack, but TNG doesn't allow us to setup a method selector instance directly.
247             Method method = clazz.getMethod( "setGroups", new Class[] { String.class, String.class } );
248             method.invoke( null, groups, excludedGroups );
249         }
250         catch ( ClassNotFoundException e )
251         {
252             throw new TestSetFailedException( e.getMessage(), e );
253         }
254         catch ( SecurityException e )
255         {
256             throw new TestSetFailedException( e.getMessage(), e );
257         }
258         catch ( NoSuchMethodException e )
259         {
260             throw new TestSetFailedException( e.getMessage(), e );
261         }
262         catch ( IllegalArgumentException e )
263         {
264             throw new TestSetFailedException( e.getMessage(), e );
265         }
266         catch ( IllegalAccessException e )
267         {
268             throw new TestSetFailedException( e.getMessage(), e );
269         }
270         catch ( InvocationTargetException e )
271         {
272             throw new TestSetFailedException( e.getMessage(), e );
273         }
274 
275         XmlMethodSelector xms = new XmlMethodSelector();
276 
277         xms.setName( clazzName );
278         // looks to need a high value
279         xms.setPriority( 9999 );
280 
281         return xms;
282     }
283 
284     public static void run( List<String> suiteFiles, String testSourceDirectory, Map options,
285                             RunListener reportManager, TestNgTestSuite suite, File reportsDirectory )
286         throws TestSetFailedException
287     {
288         TestNG testng = new TestNG( true );
289         Configurator configurator = getConfigurator( (String) options.get( "testng.configurator" ) );
290         configurator.configure( testng, options );
291         postConfigure( testng, testSourceDirectory, reportManager, suite, reportsDirectory );
292         testng.setTestSuites( suiteFiles );
293         testng.run();
294     }
295 
296     private static Configurator getConfigurator( String className )
297     {
298         try
299         {
300             return (Configurator) Class.forName( className ).newInstance();
301         }
302         catch ( InstantiationException e )
303         {
304             throw new RuntimeException( e );
305         }
306         catch ( IllegalAccessException e )
307         {
308             throw new RuntimeException( e );
309         }
310         catch ( ClassNotFoundException e )
311         {
312             throw new RuntimeException( e );
313         }
314     }
315 
316     private static void postConfigure( TestNG testNG, String sourcePath, RunListener reportManager,
317                                        TestNgTestSuite suite, File reportsDirectory )
318         throws TestSetFailedException
319     {
320         // turn off all TestNG output
321         testNG.setVerbose( 0 );
322 
323         TestNGReporter reporter = createTestNGReporter( reportManager, suite );
324         testNG.addListener( (Object) reporter );
325 
326         // FIXME: use classifier to decide if we need to pass along the source dir (onyl for JDK14)
327         if ( sourcePath != null )
328         {
329             testNG.setSourcePath( sourcePath );
330         }
331 
332         testNG.setOutputDirectory( reportsDirectory.getAbsolutePath() );
333     }
334 
335     // If we have access to IResultListener, return a ConfigurationAwareTestNGReporter
336     // But don't cause NoClassDefFoundErrors if it isn't available; just return a regular TestNGReporter instead
337     private static TestNGReporter createTestNGReporter( RunListener reportManager, TestNgTestSuite suite )
338     {
339         try
340         {
341             Class.forName( "org.testng.internal.IResultListener" );
342             Class c = Class.forName( "org.apache.maven.surefire.testng.ConfigurationAwareTestNGReporter" );
343             try
344             {
345                 Constructor ctor = c.getConstructor( new Class[] { RunListener.class, TestNgTestSuite.class } );
346                 return (TestNGReporter) ctor.newInstance( reportManager, suite );
347             }
348             catch ( Exception e )
349             {
350                 throw new RuntimeException( "Bug in ConfigurationAwareTestNGReporter", e );
351             }
352         }
353         catch ( ClassNotFoundException e )
354         {
355             return new TestNGReporter( reportManager );
356         }
357     }
358 
359 }